package fabricsdk_go

import (
	"gitee.com/wayjin/fabricsdk-go/cryptoutil"
	"github.com/hyperledger/fabric/peer/common/api"
	cb "github.com/hyperledger/fabric/protos/common"
	ab "github.com/hyperledger/fabric/protos/orderer"
	"github.com/pkg/errors"
)

type DeliverClient struct {
	Service  api.DeliverService
	ChannelID string
	TLSCertHash  []byte
	BestEffort bool
	signer  cryptoutil.Signer
}

var (
	seekOldest = &ab.SeekPosition{
		Type: &ab.SeekPosition_Oldest{
			Oldest: &ab.SeekOldest{},
		},
	}

	seekNewest = &ab.SeekPosition{
		Type: &ab.SeekPosition_Newest{
			Newest: &ab.SeekNewest{},
		},
	}
)
func seekHelper(signer cryptoutil.Signer, channelID string, position *ab.SeekPosition, tlsCertHash []byte, bestEffort bool) *cb.Envelope {
	seekInfo := &ab.SeekInfo{
		Start:    position,
		Stop:     position,
		Behavior: ab.SeekInfo_BLOCK_UNTIL_READY,
	}
	if bestEffort {
		seekInfo.ErrorResponse = ab.SeekInfo_BEST_EFFORT
	}

	env, err := cryptoutil.CreateSignedEnvelopeWithTLSBinding(
		cb.HeaderType_DELIVER_SEEK_INFO,
		channelID,
		signer,
		seekInfo,
		int32(0),
		uint64(0),
		tlsCertHash)
	if err != nil {
		return nil
	}
	return env
}

func (dc *DeliverClient) seekSpecified( blockNumber uint64) error {
	seekPosition := &ab.SeekPosition{
		Type: &ab.SeekPosition_Specified{
			Specified: &ab.SeekSpecified{
				Number: blockNumber,
			},
		},
	}

	env := seekHelper(dc.signer, dc.ChannelID, seekPosition, dc.TLSCertHash, dc.BestEffort)
	return dc.Service.Send(env)
}

func (dc *DeliverClient) seekOldest() error {
	env := seekHelper(dc.signer, dc.ChannelID, seekOldest, dc.TLSCertHash, dc.BestEffort)
	return dc.Service.Send(env)
}

func (dc *DeliverClient) seekNewest() error {
	env := seekHelper(dc.signer, dc.ChannelID, seekNewest, dc.TLSCertHash, dc.BestEffort)
	return dc.Service.Send(env)
}

func (dc *DeliverClient) readBlock() (*cb.Block, error) {
	msg, err := dc.Service.Recv()
	if err != nil {
		return nil, errors.Wrap(err, "error receiving")
	}
	switch t := msg.Type.(type) {
	case *ab.DeliverResponse_Status:
		return nil, errors.Errorf("can't read the block: %v", t)
	case *ab.DeliverResponse_Block:
		dc.Service.Recv() // Flush the success message
		return t.Block, nil
	default:
		return nil, errors.Errorf("response error: unknown type %T", t)
	}
}

// GetSpecifiedBlock gets the specified block from a peer/orderer's deliver
// service
func (dc *DeliverClient) GetSpecifiedBlock(num uint64) (*cb.Block, error) {
	err := dc.seekSpecified(num)
	if err != nil {
		return nil, errors.WithMessage(err, "error getting specified block")
	}

	return dc.readBlock()
}

// GetOldestBlock gets the oldest block from a peer/orderer's deliver service
func (dc *DeliverClient) GetOldestBlock() (*cb.Block, error) {
	err := dc.seekOldest()
	if err != nil {
		return nil, errors.WithMessage(err, "error getting oldest block")
	}

	return dc.readBlock()
}

// GetNewestBlock gets the newest block from a peer/orderer's deliver service
func (dc *DeliverClient) GetNewestBlock() (*cb.Block, error) {
	err := dc.seekNewest()
	if err != nil {
		return nil, errors.WithMessage(err, "error getting newest block")
	}

	return dc.readBlock()
}

// Close closes a deliver client's connection
func (dc *DeliverClient) Close() error {
	return dc.Service.CloseSend()
}